/*
 * Copyright (C) 2012 Tomas Shestakov. <http://code.google.com/p/jmmo/>
 */

package jmmo.engine.multifacetedactor

import akka.testkit.{ImplicitSender, TestKit}
import org.scalatest.{BeforeAndAfterAll, WordSpec}
import akka.actor.{Actor, Props, ActorSystem}

/**
 * Tests base actor functionality includes roles dependencies.
 * @author Tomas Shestakov
 * @param system actor system for testing
 * @param creator a function which returns new instance of [[jmmo.engine.multifacetedactor.MultifacetedActor]]
 */
class MultifacetedActorSpec(system: ActorSystem, creator: => MultifacetedActor, shutdown: Boolean = false) extends TestKit(system) with ImplicitSender
                                          with WordSpec with BeforeAndAfterAll {

  def this() = this(ActorSystem("TestSpec"), new MultifacetedActor {}, true)

  override def afterAll {
    if (shutdown) system.shutdown()
  }

  "A MultifacetedActor" should {

    "send no messages reply to any message if it has no role" in {
      val actor = system.actorOf(Props(new MultifacetedActor {}))
      actor ! "hello world"
      expectNoMsg()
    }

    "send message back to sender with echo role" in {
      val echoRole = new ActorRole {
        //send not internal message back to sender
        def id = "echo"
        override def preStart() {
          super.preStart()
        }
        def receive = {
          case internal: RoleEvent =>
          case msg => sender !(msg)
        }
      }

      val actor = system.actorOf(Props(
        { val newActor = creator
          newActor.addRole(echoRole)
          newActor
      }))
      actor ! "hello world"
      expectMsg("hello world")
      actor ! "second hello"
      expectMsg("second hello")
    }

    "send message back to sender only once with echo role available until first message" in {
      val onceEchoRole = new ActorRole {
        //send not internal message back to sender only once
        def id = "onceEcho"
        def receive = {
          case internal: RoleEvent =>
          case msg => {
            sender ! msg
            actor.sendInside(RoleRevoked(this))
          }
        }
      }

      val actor = system.actorOf(Props(
        { val newActor = creator
          newActor.addRole(onceEchoRole)
          newActor
      }))
      actor ! "hello world"
      expectMsg("hello world")
      actor ! "second hello"
      expectNoMsg()
    }

    "manage roles when they become available and not available and depends on each other" in {
      val firstRole = new ActorRole {
        def id = "firstRole"
        override def preStart() {}
        def receive = {
          case RoleAvailable(role) if role.id =="thirdRole" => {
            actor.sendInside(RoleAvailable(this))
          }
          case RoleRevoked(role) if role.id =="thirdRole" => {
            actor.sendInside(RoleRevoked(this))
          }
          case "muster" => sender ! id + " is ready"
        }
      }

      val secondRole = new ActorRole {
        def id = "secondRole"
        override def preStart() {}
        var first: ActorRole = null
        var third: ActorRole = null
        def receive = {
          case RoleAvailable(role) => {
            role.id match {
              case "firstRole" => {
                first = role
                if (third != null) {
                  actor.sendInside(RoleAvailable(this))
                }
              }
              case "thirdRole" => {
                third = role
                if (first != null) {
                  actor.sendInside(RoleAvailable(this))
                }
              }
              case _ =>
            }
          }
          case RoleRevoked(role) => {
            role.id match {
              case "firstRole" => {
                first = null
                if (third != null) {
                  actor.sendInside(RoleRevoked(this))
                }
              }
              case "thirdRole" => {
                third = null
                if (first != null) {
                  actor.sendInside(RoleRevoked(this))
                }
              }
              case _ =>
            }
          }
          case "muster" => sender ! id + " is ready"
        }
      }

      val thirdRole = new ActorRole {
        def id = "thirdRole"
        def receive = {
          case "muster" => sender ! id + " is ready"
        }
        override def toString = id
      }

      val master = new ActorRole {
        def id = "master"
        def receive = {
          case RoleAvailable(role) => {
            testActor ! "Avaliable role " + role.id
            if (role.id == "thirdRole") {
              testActor ! actor.role("firstRole").toString
              testActor ! actor.role("secondRole").toString
              testActor ! actor.role("thirdRole").toString
            }
            //if (role.id == "secondRole") manager.removeRole("thirdRole")
          }
          case RoleRevoked(role) => testActor ! "Revoked role " + role.id
          case "remove thirdRole" => actor.removeRole(thirdRole)
        }
      }
      
      val actor = system.actorOf(Props(
        { val newActor = creator
          newActor.addRole(master)
          newActor.addRole(firstRole)
          newActor.addRole(secondRole)
          newActor.addRole(thirdRole)
          newActor
      }))

//      receiveWhile() {
//        case msg => println(msg)
//      }

      info("Added 3 roles:")
      info("first depends of third")
      info("second depends of first and third")
      info("and third role is independent.")
      info("At begining only third role is available and can be accessed by manager.role(id).")
      info("Then first role has become available then second one.")

      expectMsg("Avaliable role thirdRole")
      expectMsg("None")
      expectMsg("None")
      expectMsg("Some(thirdRole)")
      expectMsg("Avaliable role firstRole")
      expectMsg("Avaliable role secondRole")
      expectNoMsg()

      info("Now all roles can handle messages.")
      actor ! "muster"
      //These messages can come in any order
      expectMsgAllOf("firstRole is ready", "secondRole is ready", "thirdRole is ready")
      expectNoMsg()

      info("But if we remove third role then first and second roles has become unavailable.")
      actor ! "remove thirdRole"
      expectMsg("Revoked role thirdRole")
      //These messages can come in any order
      expectMsgAllOf("Revoked role firstRole", "Revoked role secondRole")
      expectNoMsg()

      info("And cannot handle messages.")
      actor ! "muster"
      expectNoMsg()
    }

  }

}
